import 'dart:async';
import 'dart:math';

import 'package:collection/collection.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:rxdart/rxdart.dart';
import 'package:scroll_to_index/scroll_to_index.dart';

import 'dynamic_child_size/dynamic_child_size_wrap.dart';

/// A vertical scroll tab bar, which provides automated tabular index changing when content is scrolled.
/// Children's length must equal tabs 's length.
/// Children must have GlobalKey.
class VerticalScrollTabBar extends StatefulWidget {
  const VerticalScrollTabBar({
    super.key,
    required this.children,
    required this.tabs,
    this.isScrollable = true,
    this.indicatorColor,
    this.onTabChange,
    this.bottomWidget,
    this.hiddenTabBar = false,
    this.hiddenTabBarWhenLessScreenHeight = false,
    this.unselectedLabelStyle,
    this.unselectedLabelColor,
    this.labelStyle,
    this.labelColor,
  });

  final bool hiddenTabBar;

  /// Widgets to display in the tab bar's content section.
  final List<Widget> children;

  /// Tabs that will be displayed in the tab bar.
  final List<Tab> tabs;

  /// Callback when tab changed
  /// Return index of tab
  /// if index is -1, it means something wrong
  final Function(int index)? onTabChange;

  /// tabbar indicator color
  final Color? indicatorColor;

  /// is tabbar scrollable
  final bool isScrollable;
  final Widget? bottomWidget;
  final bool hiddenTabBarWhenLessScreenHeight;
  final TextStyle? unselectedLabelStyle;
  final Color? unselectedLabelColor;
  final TextStyle? labelStyle;
  final Color? labelColor;
  @override
  State<VerticalScrollTabBar> createState() => _VerticalScrollTabBarState();
}

class _VerticalScrollTabBarState extends State<VerticalScrollTabBar>
    with SingleTickerProviderStateMixin {
  /// Scroll controller for the tab bar's content section.
  late AutoScrollController scrollController = AutoScrollController();

  /// Tab controller for the tab bar.
  TabController? tabController;

  /// Mutex to prevent unexpected tab changes when tap the tab bar items.
  bool mutex = false;

  /// Timer for debounce
  Timer? timer;

  ValueNotifier<bool> isTabBarHidden = ValueNotifier(false);
  VoidCallback? onChangedSize;
  final GlobalKey _scrollKey = GlobalKey();
  final GlobalKey _tabKey = GlobalKey();

  var currentIndex = 0;
  bool loaded = false;
  final PublishSubject<int> _subject = PublishSubject();
  late StreamSubscription<int> loadedSubscription;
  // 是否执行了tabBar动画
  bool shouldTabBarAnimation = false;
  @override
  void initState() {
    loadedSubscription = _subject
        .debounceTime(const Duration(milliseconds: 150))
        .listen((index) {
      if (!loaded) {
        loaded = true;
        WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
          if (mounted) {
            setState(() {});
          }
        });
      }
      loaded = true;
    });

    onChangedSize = () {
      WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
        if (widget.hiddenTabBarWhenLessScreenHeight && !widget.hiddenTabBar) {
          isTabBarHidden.value = offstage;
        }
      });
    };
    if (widget.hiddenTabBarWhenLessScreenHeight) {
      WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
        Future.delayed(const Duration(seconds: 2)).then((value) {
          if (widget.hiddenTabBarWhenLessScreenHeight && !widget.hiddenTabBar) {
            isTabBarHidden.value = !isTabBarHidden.value;
          }
        });
      });
    }
    if (!widget.hiddenTabBar) {
      assert(widget.children.length == widget.tabs.length,
          'children.length must equal tabs.length');

      tabController = TabController(
        length: widget.tabs.length,
        vsync: this,
      );
      tabController?.addListener(() {
        shouldTabBarAnimation = true;
        widget.onTabChange?.call(tabController?.index ?? -1);
        if (tabController?.animation?.isCompleted ?? true) {
          timer?.cancel();
          timer = Timer(const Duration(milliseconds: 500), () {
            mutex = false;
            shouldTabBarAnimation = false;
          });
        }
      });

      scrollController.addListener(() {
        if (mutex && !shouldTabBarAnimation) {
          // 连续点击同一个 tab,无法执行动画
          mutex = false;
          return;
        }
        if (mutex && shouldTabBarAnimation) return;
        if (scrollController.offset >=
            scrollController.position.maxScrollExtent) {
          tabController?.animateTo(max(widget.tabs.length - 1, 0));
          return;
        }
        for (var index = widget.tabs.length - 1; index >= 0; index--) {
          var ctx = scrollController.tagMap[index]?.context;
          if (ctx == null) continue;
          var box = ctx.findRenderObject() as RenderBox;
          if (box.parentData == null || box.parentData is! BoxParentData) {
            continue;
          }
          var res = (box.parentData as BoxParentData).offset;
          if (scrollController.offset >= res.dy) {
            debugPrint(
                'scrollController.offset: ${scrollController.offset} - box:${res.dy}');
            tabController?.animateTo(index);
            return;
          }
        }
      });
    }

    super.initState();
  }

  @override
  void dispose() {
    tabController?.dispose();
    scrollController.dispose();
    loadedSubscription.cancel();
    super.dispose();
  }

  bool get offstage {
    return widget.hiddenTabBarWhenLessScreenHeight &&
        scrollController.hasClients &&
        scrollController.position.hasContentDimensions &&
        (0 == scrollController.position.maxScrollExtent ||
            scrollController.position.minScrollExtent ==
                scrollController.position.maxScrollExtent);
  }

  @override
  Widget build(BuildContext context) {
    if (!widget.hiddenTabBar &&
        (widget.tabs.isEmpty || widget.children.isEmpty)) {
      return widget.bottomWidget ?? const SizedBox();
    }
    return Stack(
      children: [
        Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          mainAxisSize: MainAxisSize.min,
          children: [
            if (!widget.hiddenTabBar)
              ValueListenableBuilder<bool>(
                valueListenable: isTabBarHidden,
                builder: (context, value, child) {
                  var offstage0 = offstage;
                  return Offstage(
                    offstage: offstage0,
                    child: TabBar(
                      key: _tabKey,
                      isScrollable: widget.isScrollable,
                      indicatorColor: widget.indicatorColor,
                      tabs: [...widget.tabs],
                      dividerColor: Colors.transparent,
                      controller: tabController,
                      labelPadding: const EdgeInsets.only(
                          right: 12, left: 12, bottom: 0, top: 0),
                      padding: const EdgeInsets.only(
                          left: 0, right: 4, bottom: 0, top: 0),
                      indicator: const BoxDecoration(),
                      indicatorSize: TabBarIndicatorSize.label,
                      tabAlignment: TabAlignment.start,
                      dividerHeight: 0,
                      onTap: (index) {
                        mutex = true;
                        scrollController.scrollToIndex(index,
                            preferPosition: AutoScrollPosition.begin);
                      },
                      unselectedLabelStyle: widget.unselectedLabelStyle,
                      labelColor: widget.labelColor,
                      unselectedLabelColor: widget.unselectedLabelColor,
                      labelStyle: widget.labelStyle,
                      overlayColor:
                          MaterialStateProperty.all(Colors.transparent),
                    ),
                  );
                },
              ),
            Flexible(
              child: _ListWidget(
                scrollKey: _scrollKey,
                controller: scrollController,
                onChangedSize: onChangedSize,
                bottomWidget: widget.bottomWidget,
                children: [
                  ...widget.children,
                ],
              ),
            ),
          ],
        ),
        if (!loaded)
          Positioned.fill(
              child: Container(
            color: Colors.white,
          )),
      ],
    );
  }
}

/// A widget that contains a list of widgets, which is content section
class _ListWidget extends StatefulWidget {
  const _ListWidget({
    Key? key,
    required this.children,
    required this.controller,
    required this.scrollKey,
    this.onChangedSize,
    this.bottomWidget,
  }) : super(key: key);

  /// Widgets to display in the tab bar's content section.
  final List<Widget> children;

  /// Scroll controller for the tab bar's content section.
  final AutoScrollController controller;
  final void Function()? onChangedSize;
  final Widget? bottomWidget;
  final GlobalKey scrollKey;
  @override
  State<_ListWidget> createState() => _ListWidgetState();
}

class _ListWidgetState extends State<_ListWidget> {
  @override
  void initState() {
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    var children = widget.children.mapIndexed((index, element) {
      return _wrapScrollTag(
        index: index,
        child: element,
      );
    }).toList();
    if (widget.bottomWidget != null) {
      children.add(widget.bottomWidget!);
    }

    return SingleChildScrollView(
      physics: const AlwaysScrollableScrollPhysics(),
      key: widget.scrollKey,
      controller: widget.controller,
      padding: EdgeInsets.zero,
      child: Column(
        mainAxisSize: MainAxisSize.min,
        children: [...children],
      ),
    );
  }

  Widget _wrapScrollTag({required Widget child, required int index}) {
    return AutoScrollTag(
      key: ValueKey(index),
      controller: widget.controller,
      index: index,
      child: ChildSizeWrap(
        child: child,
        onChildSizeChanged: (Size size) {
          debugPrint('$index - $size');
          widget.onChangedSize?.call();
        },
      ),
      // child: WidgetSizeOffsetWrapper(
      //   onWidgetSizeChange: (Size size) {
      //     widget.onChangedSize?.call();
      //   },
      //   child: child,
      // ),
    );
  }
}
